package com.netty.http;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.BasicDBObject;
import com.netty.common.Result;
import com.netty.my.MyDiskFileUpload;
import com.netty.utils.*;
import com.netty.common.DateUtil;
import com.netty.mvc.MVC;
import com.netty.my.MyDefaultHttpDataFactory;
import com.netty.websocket.Global;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil;
import org.bson.Document;
import org.springframework.util.StringUtils;

import java.io.*;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;

import static io.netty.buffer.Unpooled.copiedBuffer;

/**
 * @author gjj
 * 服务器处理器
 */
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {


    private HttpPostRequestDecoder decoder = null;

    public HashMap parameter = new HashMap();

    private HttpRequest httpRequest = null;

    public HashMap uploadTask = null;

    private long start = 0;

    private Integer startCount = 0;

    private StringBuilder stringBuilder = new StringBuilder();

    private Boolean isFail = false;

    private MVC mvc = new MVC();

    private String downloadId;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        start = System.currentTimeMillis();
    }


    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (decoder != null) {
            if (uploadTask != null) {
                String tmpFileName = uploadTask.get("tmpFileName").toString();
                String status = uploadTask.get("status").toString();
                File file = new File(MyDiskFileUpload.baseDirectory + tmpFileName);
                long length = file.length();
                if (!status.equals("完成")) {
                    uploadTask.put("currentLength", String.valueOf(length));
                    uploadTask.put("status", "已暂停");
                    MongoDbUtil mongoDbUtil = new MongoDbUtil();
                    BasicDBObject updateOldSql = new BasicDBObject("tmpFileName", tmpFileName);
                    BasicDBObject updateNewOneSql = new BasicDBObject("$set",
                            new Document().append("status", "已暂停").append("currentLength", String.valueOf(length)));
                    mongoDbUtil.updateDocument(updateOldSql, updateNewOneSql, Constants.UPLOAD_TASK_COLLECTION);
                    mongoDbUtil.close();
                }
                File newFile = new File(MyDiskFileUpload.baseDirectory + tmpFileName + "new");
                file.renameTo(newFile);
                decoder.cleanFiles();
                newFile.renameTo(file);
                newFile.delete();
            } else {
                decoder.cleanFiles();
            }

            if (!StringUtils.isEmpty(downloadId)) {
                MongoDbUtil mongoDbUtil = new MongoDbUtil();
                HashMap<String, Object> downloadTask = mongoDbUtil.queryByID(Constants.DOWNLOAD_TASK_COLLECTION, downloadId);
                String status = downloadTask.get("status").toString();
                if (!status.equals("完成")) {
                    BasicDBObject updateOldSql = new BasicDBObject("_id", downloadId);
                    BasicDBObject updateNewOneSql = new BasicDBObject("$set",
                            new Document().append("status", "已暂停"));
                    mongoDbUtil.updateDocument(updateOldSql, updateNewOneSql, Constants.DOWNLOAD_TASK_COLLECTION);
                    Object tmpFileName = downloadTask.get("tmpFileName");
                    if (tmpFileName != null) {
                        File file = new File(Constants.BASE_DIRECTORY + tmpFileName);
                        file.delete();
                    }
                }
                mongoDbUtil.close();

            }
        }

    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
        try {
            if (isFail) {
                return;
            }
            if (msg instanceof HttpRequest) {
                //处于读取请求行、请求头
                //将msg转为HttpRequest
                httpRequest = (HttpRequest) msg;
                //判断是否有带netty前缀
                URI uri = new URI(httpRequest.uri());
                String path = uri.getPath();
                if (!path.startsWith("/netty")) {
                    throw new RuntimeException("404");
                } else {
                    //get请求 //获取get请求的参数
                    QueryStringDecoder decoderQuery = new QueryStringDecoder(httpRequest.uri());
                    Map<String, List<String>> uriAttributes = decoderQuery.parameters();
                    for (Map.Entry<String, List<String>> attr : uriAttributes.entrySet()) {
                        for (String attrVal : attr.getValue()) {
                            if (attr.getKey().toString().equals("uploadId")) {
                                String uploadId = attrVal;
                                if (path.contains("file/save")) {
                                    MongoDbUtil mongoDbUtil = new MongoDbUtil();
                                    uploadTask = mongoDbUtil.queryByID(Constants.UPLOAD_TASK_COLLECTION, uploadId);
                                    if (uploadTask != null) {
                                        BasicDBObject updateOldSql = new BasicDBObject("_id", uploadId);
                                        BasicDBObject updateNewOneSql = new BasicDBObject("$set",
                                                new Document().append("status", "上传中"));
                                        mongoDbUtil.updateDocument(updateOldSql, updateNewOneSql, Constants.UPLOAD_TASK_COLLECTION);
                                    }
                                    mongoDbUtil.close();
                                }
                            }
                            parameter.put(attr.getKey(), attrVal);
                        }
                    }
                    //生成decoder，以便从decoder获取httpRequest上的数据
                    decoder = new HttpPostRequestDecoder(new MyDefaultHttpDataFactory(MyDefaultHttpDataFactory.MINSIZE, this), httpRequest);
                }

            } else if (msg instanceof HttpContent) {
                HttpMethod httpMethod = httpRequest.method();
                String contentType = getContentType();
                if (httpMethod == null) {
                    writeResponse(ctx.channel(), "ok");
                } else {
                    //处于读取请求body
                    HttpContent httpContent = (HttpContent) msg;
                    //判断是否是最后一个content
                    if (httpContent instanceof LastHttpContent) {
                        //是最后一个，则表示请求结束，返回数据给客户端
                        if (httpMethod.equals(HttpMethod.POST)) {
                            if (contentType.equals("application/json")) {
                                ByteBuf content = httpContent.content();
                                //再获取最后的传输报文json
                                if (content != null) {
                                    String json = content.toString(CharsetUtil.UTF_8);
                                    stringBuilder.append(json);
                                }
                                String json = stringBuilder.toString();
                                stringBuilder.setLength(0);
                                //得到json
                                parameter.put("json", json);
                            } else if (contentType.equals("application/x-www-form-urlencoded")) {
                                ByteBuf content = httpContent.content();
                                if (content != null) {
                                    String string = content.toString(CharsetUtil.UTF_8);
                                    stringBuilder.append(string);
                                }
                                String string = stringBuilder.toString();
                                if (content != null) {
                                    String paramStr = string;
                                    String[] splits = paramStr.split("&");
                                    for (String split : splits) {
                                        String[] param = split.split("=");
                                        String key = param[0];
                                        Object value = null;
                                        if (param.length > 1) {
                                            value = URLDecoder.decode(param[1], "UTF-8");
                                        }
                                        parameter.put(key, value);
                                    }
                                }
                            } else if (contentType.startsWith("multipart/form-data")) {

                                if (decoder != null) {
                                    //把请求body也放进decoder中
                                    decoder.offer(httpContent);
                                    readData(decoder);
                                }
                            }

                        }
                        String methodType = httpMethod.name().toLowerCase();
                        Object object = mvc.deal(httpRequest.uri(), methodType, parameter);
                        if (object instanceof HashMap && ((HashMap) object).containsKey("file")) {
                            //写出数据
                            File file = (File) ((HashMap) object).get("file");
                            String fileName = ((HashMap) object).get("fileName").toString();
                            downloadId = ((HashMap) object).get("downloadId").toString();
                            download(ctx, file, fileName);
                        } else {
                            //写出object
                            writeResponse(ctx.channel(), (String) object);
                        }
                    } else {
                        if (contentType.startsWith("application/json") || contentType.startsWith("application/x-www-form-urlencoded")) {
                            //json请求，直接从content获取数据
                            ByteBuf content = httpContent.content();
                            String string = content.toString(CharsetUtil.UTF_8);
                            stringBuilder.append(string);
                        } else if (contentType.startsWith("multipart/form-data")) {
                            if (decoder != null) {
                                //把请求body也放进decoder中
                                //offer进去，如果是文件上传，这行代码才会最终生成临时文件
                                decoder.offer(httpContent);
                                //读写decoder中的数据
                                readData(decoder);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
            Result result = Result.errorResult("系统异常" + message);
            ObjectMapper objectMapper = new ObjectMapper();
            String s = null;
            try {
                s = objectMapper.writeValueAsString(result);
            } catch (JsonProcessingException e1) {
                e1.printStackTrace();
            }
            writeResponse(ctx.channel(), s);
            isFail = true;
        }
    }

    private void readData(HttpPostRequestDecoder decoder) throws Exception {
        try {
            while (decoder.hasNext()) {
                InterfaceHttpData next = decoder.next();
                if (next != null) {
                    //当最后的片到文件，则为100%文件，进行写入
                    writeHttpData(next);
                }
            }
            //文件上传，显示进度
            HttpData partialContent = (HttpData) decoder.currentPartialHttpData();
            if (partialContent != null) {
                if (partialContent.definedLength() > 0) {
                    //能整除 则可以进行推送
                    if (parameter.get("websocketUser") != null) {
                        if (startCount % Constants.PUSH_COUNT == 0) {
                            long currentTimeMillis = System.currentTimeMillis();
                            long useTime = currentTimeMillis - start;
                            HashMap size = Utils.getSize(Long.valueOf(partialContent.length() + ""));
                            String unit = size.get("unit").toString();
                            String resultSize = size.get("resultSize").toString();
                            double second = Double.valueOf(useTime) / 1000D;
                            BigDecimal bg = new BigDecimal((Double.valueOf(resultSize) / second));
                            double result = bg.setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();
                            //推送上传进度
                            HashMap map = new HashMap();
                            //任务id
                            map.put("uploadId", parameter.get("uploadId"));
                            //传输长度
                            long currentLength = 0L;
                            if (uploadTask.containsKey("currentLength")) {
                                String currentLengthstr = uploadTask.get("currentLength").toString();
                                currentLength = Long.valueOf(currentLengthstr);
                            }
                            map.put("transferLength", partialContent.length() + currentLength);
                            map.put("speed", result + " " + unit + "/S");
                            //传输总长
                            map.put("totalLength", Long.valueOf(uploadTask.get("fileLength").toString()));
                            Global.push(parameter.get("websocketUser").toString(), map, Global.UPLOAD_TYPE, false);
                        }
                        startCount = startCount + 1;
                    }
                }
            }
        } catch (HttpPostRequestDecoder.EndOfDataDecoderException e) {
//            System.out.println("end of content chunk by chunk");
        }

    }

    private String getContentType() {
        //判断请求类型content-type
        HttpHeaders headers = httpRequest.headers();
        if (httpRequest.method().equals(HttpMethod.POST)) {
            Map.Entry<String, String> stringStringEntry = headers.entries().stream().filter(entry -> {
                return entry.getKey().toLowerCase().equals("content-type");
            }).findFirst().get();
            return stringStringEntry.getValue().toLowerCase();
        }
        return null;
    }


    private void writeHttpData(InterfaceHttpData next) throws Exception {
        if (next.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
            //表单属性  表单请求
            //业务逻辑.....
            Attribute attribute = (Attribute) next;
            parameter.put(attribute.getName(), attribute.getValue());
        } else if (next.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
            //文件上传
            FileUpload fileUpload = (FileUpload) next;
            if (fileUpload.isCompleted()) {
                File dest = fileUpload.getFile();
                long length = dest.length();
                String fileName = uploadTask.get("fileName").toString();
                String uuid = uploadTask.get("_id").toString();
                String parent = dest.getParent();
                String uploadFileName = fileName.replaceAll(uuid, "");
                File newFile = new File(parent + File.separator + uploadFileName);
                dest.renameTo(newFile);
                parameter.put(fileUpload.getName(), newFile);
                //上传完成，更改mongodb记录
                uploadTask.put("status", "完成");
                String name = dest.getName();
                MongoDbUtil mongoDbUtil = new MongoDbUtil();
                BasicDBObject updateOldSql = new BasicDBObject("tmpFileName", name);
                BasicDBObject updateNewOneSql = new BasicDBObject("$set",
                        new Document().append("status", "完成").append("currentLength", length)
                                .append("finishTime", DateUtil.format(new Date(), DateUtil.DATE_TIME_PATTERN)));
                mongoDbUtil.updateDocument(updateOldSql, updateNewOneSql, Constants.UPLOAD_TASK_COLLECTION);
                mongoDbUtil.close();

                //推送文件上传完成
                HashMap map = new HashMap();
                //任务id
                map.put("uploadId", parameter.get("uploadId"));
                if (parameter.get("websocketUser") != null) {
                    Global.push(parameter.get("websocketUser").toString(), map, Global.UPLOAD_TYPE, true);
                }
            }
        }
    }


    private void writeResponse(Channel channel, String msg) {
        //将组装的sb封装成bytebuf
        ByteBuf buf = copiedBuffer(msg, CharsetUtil.UTF_8);
        //根据buf 获取response
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
        //设置响应头 content_type
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        if (!StringUtil.isNullOrEmpty(httpRequest.headers().get("requestId"))) {
            response.headers().set("requestId", httpRequest.headers().get("requestId"));
        }
        //写出数据
        if (channel.isWritable()) {
            channel.writeAndFlush(response);
        }
        channel.close();
        //删除临时文件
        if (decoder != null) {
            decoder.cleanFiles();
        }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.channel().close();
    }


    private void download(ChannelHandlerContext ctx, File file, final String fileName) throws Exception {
        MongoDbUtil mongoDbUtil = new MongoDbUtil();
        HashMap<String, Object> downloadTask = mongoDbUtil.queryByID(Constants.DOWNLOAD_TASK_COLLECTION, downloadId);
        mongoDbUtil.close();
        Long oriFileLength = Long.valueOf(downloadTask.get("fileLength").toString());
        final RandomAccessFile raf = new RandomAccessFile(file, "r");
        long fileLength = raf.length();
        Long currentLength = oriFileLength - fileLength;
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/octet-stream");
        response.headers().set("fileName", fileName);
        response.headers().add(HttpHeaderNames.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", URLEncoder.encode(fileName, "UTF-8")));
        if (!StringUtil.isNullOrEmpty(httpRequest.headers().get("requestId"))) {
            response.headers().set("requestId", httpRequest.headers().get("requestId"));
        }
        ctx.write(response);
        ChannelFuture sendFileFuture = ctx.write(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)), ctx.newProgressivePromise());
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            @Override
            public void operationComplete(ChannelProgressiveFuture future)
                    throws Exception {
                raf.close();
            }

            @Override
            public void operationProgressed(ChannelProgressiveFuture future,
                                            long progress, long total) throws Exception {
                if (progress==total){
                    MongoDbUtil mongoDbUtil = new MongoDbUtil();
                    HashMap<String, Object> stringObjectHashMap = mongoDbUtil.queryByID(Constants.DOWNLOAD_TASK_COLLECTION,downloadId);
                    String status = stringObjectHashMap.get("status").toString();
                    if (!status.equals("完成")){
                        BasicDBObject updateOldSql = new BasicDBObject("_id", downloadId);
                        BasicDBObject updateNewOneSql = new BasicDBObject("$set",
                                new Document().append("status", "完成"));
                        mongoDbUtil.updateDocument(updateOldSql, updateNewOneSql, Constants.DOWNLOAD_TASK_COLLECTION);

                        Object tmpFileName = stringObjectHashMap.get("tmpFileName");
                        if (tmpFileName != null) {
                            File file = new File(Constants.BASE_DIRECTORY + tmpFileName);
                            file.delete();
                        }
                        if (parameter.get("websocketUser") != null) {
                            HashMap map = new HashMap();
                            map.put("downloadId", downloadId);
                            Global.push(parameter.get("websocketUser").toString(), map, Global.DOWNLOAD_TYPE, true);
                        }
                    }
                    mongoDbUtil.close();
                }else{
                    if (parameter.get("websocketUser") != null) {
                        if (startCount % Constants.PUSH_COUNT == 0) {
                            long currentTimeMillis = System.currentTimeMillis();
                            long useTime = currentTimeMillis - start;
                            HashMap size = Utils.getSize(progress);
                            String unit = size.get("unit").toString();
                            String resultSize = size.get("resultSize").toString();
                            double second = Double.valueOf(useTime) / 1000D;
                            BigDecimal bg = new BigDecimal((Double.valueOf(resultSize) / second));
                            double result = bg.setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();
                            HashMap map = new HashMap();
                            map.put("downloadId", downloadId);
                            map.put("transferLength", progress + currentLength);
                            map.put("speed", result + " " + unit + "/S");
                            //传输总长
                            map.put("totalLength", oriFileLength);
                            Global.push(parameter.get("websocketUser").toString(), map, Global.DOWNLOAD_TYPE, false);
                        }
                        startCount = startCount + 1;
                    }
                }
            }
        });
        if (ctx.channel().isWritable()) {
            ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        }
        //删除临时文件
        if (decoder != null) {
            decoder.cleanFiles();
        }
    }
}
